package com.server.questionbank.snframe.service.VerificationCode;

import com.server.questionbank.snframe.config.interceptor.JwtInterceptor;
import com.server.questionbank.snframe.domain.VerificationCode.ImageCode;
import com.server.questionbank.snframe.config.verificationCode.ImageCodeConfig;
import com.server.questionbank.snframe.config.verificationCode.SmsCodeConfig;
import com.server.questionbank.snframe.domain.ResDTO.ResponseResult;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import com.aliyun.teaopenapi.models.Config;
import com.aliyun.dysmsapi20170525.Client;
import com.aliyun.dysmsapi20170525.models.SendSmsRequest;
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class VerificationCodeServiceImpl implements VerificationCodeService {
    private static final Logger logger = LoggerFactory.getLogger(VerificationCodeServiceImpl.class);

    @Autowired // 注入图片验证码配置类
    private ImageCodeConfig imageCodeConfig;

    @Autowired // 注入短信验证码配置类
    private SmsCodeConfig smsCodeConfig;

//    @Autowired // 注入邮箱验证码配置类
//    private EmailCodeConfig emailCodeConfig;

    @Autowired // 注入redis模板类
    private StringRedisTemplate redisTemplate;

    private static Font customFont;

    /**
     * 🌟 Spring 启动时，自动初始化
     */
    @PostConstruct
    void initialize()
    {
        logger.info("🔄 初始化 字体...");
        // 加载自定义字体
        customFont = loadFontSafely(imageCodeConfig.getFontName(), imageCodeConfig.getFontSize());
    }

    // 获取图片验证码
    @Override
    public ResponseResult<ImageCode> getImageCode() {
        // 创建图片验证码
        BufferedImage imageCode = new BufferedImage(
                imageCodeConfig.getWidth(),
                imageCodeConfig.getHeight(),
                BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = imageCode.createGraphics();

        // 设置抗锯齿
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        // 设置背景色
        g2d.setColor(new Color(255, 255, 255, 0));
        g2d.fillRect(0, 0, imageCodeConfig.getWidth(), imageCodeConfig.getHeight());

        // 生成随机验证码
        int codeLength = imageCodeConfig.getCodeLength();
        StringBuilder codeBuilder = new StringBuilder(codeLength); // 预分配容量
        for (int i = 0; i < imageCodeConfig.getCodeLength(); i++) {
            codeBuilder.append(imageCodeConfig.getCharacter().charAt((int) (Math.random() * imageCodeConfig.getCharacter().length())));
        }
        String code = codeBuilder.toString();

        // 生成随机 session 唯一 Id
        String sessionId;
        do {
            sessionId = "image_code_session_" + UUID.randomUUID();
        } while (Boolean.TRUE.equals(redisTemplate.hasKey(sessionId)));

        logger.info("图片验证码：[sessionId] [{}]  ,  [验证码] [{}]", sessionId, code);

        // 存储验证码到 Redis 中
        redisTemplate.opsForValue().set(sessionId, code, imageCodeConfig.getExpiration(), TimeUnit.SECONDS);

        // 绘制干扰点
        for (int i = 0; i < 100; i++) {
            // 随机颜色
            g2d.setColor(new Color((int) (Math.random() * 255), (int) (Math.random() * 255), (int) (Math.random() * 255)));
            // 绘制干扰点
            g2d.drawOval((int) (Math.random() * imageCodeConfig.getWidth()), (int) (Math.random() * imageCodeConfig.getHeight()), 1, 1);
        }

        // 设置字体
        g2d.setFont(customFont);
        for (int i = 0; i < code.length(); i++) {
            // 随机颜色
            g2d.setColor(new Color((int) (Math.random() * 180), (int) (Math.random() * 180), (int) (Math.random() * 180)));

            // 字符的宽度和高度
            int charWidth = g2d.getFontMetrics().charWidth(code.charAt(i));
            int charHeight = imageCodeConfig.getFontSize();

            // 计算旋转的中心点
            int centerX = (imageCodeConfig.getWidth() / imageCodeConfig.getCodeLength()) * i + charWidth / 2;
            int centerY = imageCodeConfig.getHeight() / 2;

            // 限制旋转角度，防止字符过度旋转
            double rotateAngle = Math.random() * 20 - 10;  // 随机旋转角度：-10到10度

            // 计算旋转后的字符位置
            g2d.rotate(Math.toRadians(rotateAngle), centerX, centerY);

            // 绘制字符
            g2d.drawString(code.charAt(i) + "", (imageCodeConfig.getWidth() / imageCodeConfig.getCodeLength()) * i, charHeight);

            // 恢复旋转
            g2d.rotate(Math.toRadians(-rotateAngle), centerX, centerY);  // 恢复到原来的角度
        }

        // 绘制 干扰线
        for (int i = 0; i < 4; i++) {
            g2d.setColor(new Color(
                    (int) (Math.random() * 255),
                    (int) (Math.random() * 255),
                    (int) (Math.random() * 255))
            );

            if (i % 2 == 0) {
                // **偶数编号（贯穿整个图片）**
                int x1 = 0;  // 起点在左侧边界
                int y1 = (int) (Math.random() * imageCodeConfig.getHeight());
                int x2 = imageCodeConfig.getWidth(); // 终点在右侧边界
                int y2 = (int) (Math.random() * imageCodeConfig.getHeight());

                g2d.drawLine(x1, y1, x2, y2);
            } else {
                // **奇数编号（短线）**
                int x1 = (int) (Math.random() * imageCodeConfig.getWidth());
                int y1 = (int) (Math.random() * imageCodeConfig.getHeight());
                int x2 = x1 + (int) (Math.random() * 20) - 10; // 长度随机（短线）
                int y2 = y1 + (int) (Math.random() * 20) - 10;

                g2d.drawLine(x1, y1, x2, y2);
            }
        }

        // 释放资源
        g2d.dispose();

        // 将图片转换为 base64
        try {
            // 创建一个字节输出流
            ByteArrayOutputStream bao = new ByteArrayOutputStream();

            // 将BufferedImage写入到输出流中，这里使用PNG格式
            ImageIO.write(imageCode, "png", bao);

            // 将输出流转换为字节数组
            byte[] imageBytes = bao.toByteArray();

            // 使用Base64编码器编码字节数组
            String base64String = Base64.getEncoder().encodeToString(imageBytes);

            // 创建ImageCode对象
            ImageCode imageCodeResult = new ImageCode();
            imageCodeResult.setCode("data:image/png;base64," + base64String);
            imageCodeResult.setSessionId(sessionId);

            // 返回Base64编码的字符串
            return ResponseResult.success(imageCodeResult);
        } catch (Exception e) {
            e.printStackTrace();
            return ResponseResult.error();
        }
    }

    // 发送短信验证码
    @Override
    public ResponseResult<String> sendSmsCode(String phoneNumber) {
        String clientIp = JwtInterceptor.getCurrentIp();
        String redisIPSendIntervalKey = "sms_ip_send_interval_" + clientIp; // 记录 IP 限制
        String redisPhoneSendIntervalKey = "sms_phone_send_interval_" + phoneNumber; // 记录 手机号 限制

        logger.info("[发送短信][{}]  ,  [IP] [{}]", phoneNumber, clientIp);
        if (phoneNumber == null || phoneNumber.length() != 11) {
            logger.warn("[手机号错误]：请输入正确手机号");
            return ResponseResult.error("请输入正确手机号");
        }
        // 检查IP 规定时间内是否已成功发送
        Long lastIPSendTime = redisTemplate.getExpire(redisIPSendIntervalKey, TimeUnit.SECONDS);
        if (lastIPSendTime != null && lastIPSendTime > 0) {
            logger.info("[IP限制]：请稍后重试，{} 秒后可重试", lastIPSendTime);
            return ResponseResult.error("请稍后再试，" + lastIPSendTime + " 秒后可重试");
        }

        // 检查手机号 规定时间内是否已成功发送
        Long lastPhoneSendTime = redisTemplate.getExpire(redisPhoneSendIntervalKey, TimeUnit.SECONDS);
        if (lastPhoneSendTime != null && lastPhoneSendTime > 0) {
            logger.info("[手机号限制]：请稍后重试，{} 秒后可重试", lastPhoneSendTime);
            return ResponseResult.error("此号码需要" + lastPhoneSendTime + " 秒后才可重新发送");
        }

        // 生成随机验证码
        int codeLength = smsCodeConfig.getCodeLength();
        StringBuilder codeBuilder = new StringBuilder(codeLength); // 预分配容量
        for (int i = 0; i < smsCodeConfig.getCodeLength(); i++) {
            codeBuilder.append(smsCodeConfig.getCharacter().charAt((int) (Math.random() * smsCodeConfig.getCharacter().length())));
        }
        String code = codeBuilder.toString();

        try {
            // 初始化配置
            Config config = new Config()
                    .setAccessKeyId(smsCodeConfig.getAccessKeyId())
                    .setAccessKeySecret(smsCodeConfig.getAccessKeySecret())
                    .setEndpoint("dysmsapi.aliyuncs.com"); // 短信服务 API 地址

            // 初始化客户端
            Client client = new Client(config);
            SendSmsRequest sendSmsRequest  = new SendSmsRequest()
                    .setSignName(smsCodeConfig.getSignName())
                    .setTemplateCode(smsCodeConfig.getTemplateCode())
                    .setPhoneNumbers(phoneNumber)
                    .setTemplateParam("{\"code\":\"" + code + "\"}");

            // 获取响应对象
            SendSmsResponse sendSmsResponse = client.sendSms(sendSmsRequest);
            //判断是否发送成功
            if(sendSmsResponse.getBody().code != null) {
                if(sendSmsResponse.getBody().code.equalsIgnoreCase("OK"))
                {
                    // 存储验证码到 Redis 中
                    redisTemplate.opsForValue().set("sms_code_" + phoneNumber, code, smsCodeConfig.getExpiration(), TimeUnit.SECONDS);
                    // 记录 IP 限制
                    redisTemplate.opsForValue().set(redisIPSendIntervalKey, "1", smsCodeConfig.getIpSendInterval(), TimeUnit.SECONDS);
                    // 记录 手机号 限制
                    redisTemplate.opsForValue().set(redisPhoneSendIntervalKey, "1", smsCodeConfig.getPhoneSendInterval(), TimeUnit.SECONDS);

                    return ResponseResult.success("短信验证码发送成功");
                } else if (sendSmsResponse.getBody().code.equalsIgnoreCase("isv.MOBILE_NUMBER_ILLEGAL"))
                {
                    return ResponseResult.error("手机号码格式错误");
                } else if (sendSmsResponse.getBody().code.equalsIgnoreCase("INVALID_NUMBER")
                        || sendSmsResponse.getBody().code.equalsIgnoreCase("MOBILE_NOT_ON_SERVICE"))
                {
                    return ResponseResult.error("手机号码状态异常");
                } else if (sendSmsResponse.getBody().code.equalsIgnoreCase("MOBLLE_TERMINAL_ERROR"))
                {
                    return ResponseResult.error("短信已被手机拦截");
                }
            }
            return ResponseResult.error("短信验证码发送失败");
        } catch (Exception e) {
            e.printStackTrace();
            return ResponseResult.error("短信验证码发送失败");
        }
    }

    @Override
    public ResponseResult<String> sendEmailCode(String email) {
        return null;
    }

    @Override
    public ResponseResult<String> checkImageCode(ImageCode imageCode) {
        if (imageCode == null || imageCode.getCode() == null || imageCode.getSessionId() == null) {
            return ResponseResult.error("验证码错误");
        }
        // 1. 检查 Redis 是否存在该验证码
        String storedCode = redisTemplate.opsForValue().get(imageCode.getSessionId());

        if (storedCode == null) {
            return ResponseResult.error("验证码已过期或不存在");
        }

        // 2. 忽略大小写比较验证码是否匹配
        if (!storedCode.equalsIgnoreCase(imageCode.getCode())) {
            return ResponseResult.error("验证码错误");
        }

        // 3. 验证通过后删除 Redis 中的验证码，防止重复使用
        redisTemplate.delete(imageCode.getSessionId());

        return ResponseResult.success("验证码正确");
    }

    @Override
    public ResponseResult<String> checkSmsCode(String phoneNumber, String code) {
        // 1. 检查 Redis 是否存在该验证码
        String storedCode = redisTemplate.opsForValue().get("sms_code_" + phoneNumber);

        if (storedCode == null) {
            return ResponseResult.error("验证码已过期或不存在");
        }

        // 2. 忽略大小写比较验证码是否匹配
        if (!storedCode.equalsIgnoreCase(code)) {
            return ResponseResult.error("验证码错误");
        }

        // 3. 验证通过后删除 Redis 中的验证码，防止重复使用
        redisTemplate.delete(phoneNumber);

        return ResponseResult.success("验证码正确");
    }

    @Override
    public ResponseResult<String> checkEmailCode(String email, String code) {
        return null;
    }

    // 字体加载
    private Font loadFontSafely(String fontName, int fontSize) {
        // 1. 首先检查系统是否已安装该字体
        Font systemFont = findSystemFont(fontName, fontSize);
        if (systemFont != null) {
            return systemFont;
        }

        // 2. 尝试加载外部或嵌入的TTF字体
        try (InputStream fontStream = getFontResourceStream(fontName)) {
            if (fontStream != null) {
                return Font.createFont(Font.TRUETYPE_FONT, fontStream)
                        .deriveFont(Font.PLAIN, fontSize);
            }
        } catch (FontFormatException | IOException e) {
            logger.warn("❌ 字体文件加载失败: {}", fontName, e);
        }

        // 3. 系统字体回退策略
        return getFallbackFont(fontSize);
    }

    // 检查系统已安装字体
    private Font findSystemFont(String fontName, int fontSize) {
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        Font[] allFonts = ge.getAllFonts();

        // 精确匹配字体名称
        for (Font font : allFonts) {
            if (fontName.equalsIgnoreCase(font.getFontName())) {
                logger.info("✅  成功匹配字体名称：{}", fontName);
                return font.deriveFont(Font.PLAIN, fontSize);
            }
        }

        // 模糊匹配字体家族
        for (Font font : allFonts) {
            if (fontName.equalsIgnoreCase(font.getFamily())) {
                logger.info("✅  成功匹配字体家族：{}", fontName);
                return font.deriveFont(Font.PLAIN, fontSize);
            }
        }
        logger.warn("⚠️ 字体匹配失败：{}", fontName);
        return null;
    }

    // 系统回退字体（按优先级尝试）
    private Font getFallbackFont(int fontSize) {
        String[] fallbackList = {
                "SimSun",    // Windows 宋体
                "Microsoft YaHei",  // Windows 微软雅黑
                "Songti SC", // macOS 宋体
                "Noto Sans CJK SC", // Linux
                Font.SERIF,  // 通用衬线
                Font.SANS_SERIF // 通用无衬线
        };

        for (String name : fallbackList) {
            try {
                Font font = new Font(name, Font.PLAIN, fontSize);
                if (!font.getFamily().equals(Font.DIALOG)) {
                    logger.warn("⚠️ 已回退到系统字体: {}", name);
                    return font;
                }
            } catch (Exception e) {
                logger.warn("⚠️ 回退字体 {} 不可用", name);
            }
        }

        logger.warn("⚠️ 已回退到字体: {}", "MONOSPACED");
        // 终极回退
        return new Font(Font.MONOSPACED, Font.PLAIN, fontSize);
    }

    // 获取字体文件流
    private InputStream getFontResourceStream(String fontPath) {
        // 1. 尝试外部文件（优先级最高）
        Path externalFile = Paths.get("config", fontPath.split("/")); // 跨平台路径分隔符处理
        if (Files.exists(externalFile)) {
            try {
                InputStream externalInputStream = Files.newInputStream(externalFile);
                logger.info("✅  成功加载字体：{}", externalFile);
                return externalInputStream;
            } catch (IOException e) {
                logger.warn("⚠️ 外部字体加载失败，回退内置资源: {}", externalFile, e);
            }
        }
        else
        {
            logger.warn("⚠️ 外部字体文件未找到: {}", externalFile);
        }

        // 2. 尝试从 ClassLoader 加载（兼容 JAR）
        InputStream classLoaderInputStream = VerificationCodeServiceImpl.class
                .getClassLoader()
                .getResourceAsStream(fontPath);

        // 3. 备用加载方式（极端情况兜底）
        if (classLoaderInputStream == null) {
            logger.warn("⚠️ 字体文件未找到: {}", fontPath);
            classLoaderInputStream = VerificationCodeServiceImpl.class.getResourceAsStream("/" + fontPath);
            if (classLoaderInputStream == null) {
                logger.warn("⚠️ 字体文件未找到: {}", "/" + fontPath);
            }
            else
            {
                logger.info("✅  成功加载字体：{}", "/" + fontPath);
            }
        }
        else
        {
            logger.info("✅  成功加载字体：{}", fontPath);
        }
        return classLoaderInputStream;
    }
}
